Есть ли язык программирования, где 1/6 ведет себя так же, как 1.0 / 6.0?

11

Когда я программировал на C ++ несколько дней назад, я допустил эту ошибку (что у меня есть история создания этого!). В одной части моего кода у меня было 1/6, и я ожидал, что это будет 0,16666666666, что не так. Как вы все знаете, результат 0 - C, C ++, Java, Python, все ведут себя одинаково.

Я публикую это на своей странице в Facebook, и сейчас идет дискуссия о том, существует ли язык программирования, который 1/6ведет себя так же, как и 1.0/6.0.

Pouya
источник
5
Haskell. 1/6 = 0,16666666666666666
t123
PowerShell генерирует 0.166666666666667, что удивило меня, поскольку 1 является целым числом. Держу пари, есть и другие языки .NET, которые генерируют ожидаемое вами значение.
JohnL
Теоретически их существует неограниченное количество. Вот еще: Rebol , а также производные, такие как Orca, Red и так далее. >> 1 / 6->== 0.166666666666667
Изката
Луа делает это. Он имеет только один тип Number , который обычно совпадает с double.
Мачадо
В Clojure 1/6фактически 1/6 (дробный тип), который по принуждению Doubleравен 1.66666 ...
kaoD

Ответы:

17

Все забыли Паскаль?

1/6доходность 0.1666666...(с любой точностью поддерживается).

1 div 6 доходность 0

Можно утверждать, является ли правило C ошибкой. Почти все арифметические операторы Си, в которых операнды имеют одинаковый тип, дают результат того же типа. Есть что-то, что нужно сказать для последовательности.

Кроме того, поскольку C в первую очередь нацелен на код системного уровня, большинство программ на C вообще не использует плавающую точку. Одно время, случайное добавление кода с плавающей точкой в ​​программу, которая иначе не нуждалась в этом, могло быть серьезной проблемой. Это, вероятно, все еще имеет место для небольших встроенных систем, которые, опять же, являются основной целью для C.

В большинстве программ на C усечение целочисленного деления, вероятно, как раз то, что вам нужно.

Если 1 / 6получен результат с плавающей точкой в ​​C, то:

  • Это было бы несоответствием в языке.
  • Стандарт должен был бы сделать произвольный выбор того, какой тип с плавающей точкой использовать для результата ( doubleможет показаться естественным выбором, но вы могли бы предпочесть дополнительную точность long double)
  • В языке все равно должна быть операция целочисленного деления; выполнение сложения с плавающей точкой, а затем усечение, вероятно, будет недостаточно.

C мог бы предоставить отдельные операторы для двух видов деления, но второй пункт выше все равно будет применяться: какой из трех типов с плавающей точкой будет использоваться для результата? И поскольку достаточно просто получить деление с плавающей запятой, если вам это нужно (используйте константу с плавающей запятой для одного или обоих операндов или приведите один или оба операнда к типу с плавающей запятой), очевидно, что Я считаю это важным.

В версии руководства C на 1974 год (это за 4 года до публикации первого издания K & R) Ричи даже не упоминает о возможной путанице:

Двоичный / оператор указывает на деление. Применяются те же соображения, что и для умножения

который говорит, что если оба операнда имеют тип intили char, результат имеет тип int.

Да, это является источником путаницы для некоторых программистов на C, особенно начинающих, но C не известен тем, что он очень новичок.

Кит Томпсон
источник
5
Паскаль. Получение деталей прямо перед тем, как С понял их неправильно. ™
Мейсон Уилер
А потом Алгол стал лучше своих преемников (в числе которых стоят и Си, и Паскаль).
AProgrammer
Паскаль - правильно продумать детали (дать или взять коэффициент 10)
Мартин Беккет,
1
Я изначально написал 1.666666..., что явно не так. Мое извинительное оправдание в том, что напечатанная мною программа тестирования на Паскале1.6666666666666667E-0001
Кит Томпсон,
16

На самом деле это поведение было изменено в Python 3, и теперь оно ведет себя так, как вы ожидаете ( //теперь используется для целочисленного деления).

sepp2k
источник
Спасибо. Какие еще языки программирования ты имеешь в виду? Основная семья может быть?
Pouya
1
@Pouya: это поведение является стандартным для семьи Паскаль. /всегда создает значение с плавающей запятой, а divдля целочисленного деления используется отдельный оператор ( ).
Мейсон Уилер
13

Из выдающихся языков JavaScript. 1,0 / 6,0 = 1/6 = 0,16666666666666666.

Я не вижу в этом ничего удивительного. Как правило, если язык различает целочисленные и числовые типы с плавающей запятой, разделение двух целых чисел приведет к усеченному целому числу вместо числа с плавающей запятой. Если этого не произойдет, скорее всего, он будет по умолчанию для операций с плавающей запятой. Это должно быть ожидаемое поведение со стороны программиста.

Просто имейте в виду, что здесь могут присутствовать и другие вещи, такие как уже упомянутый отдельный оператор целочисленного деления или неявное приведение типов.

scrwtp
источник
6
Это не «правило большого пальца». Это правило Си и несколько языков, которые слепо копируют ошибки Си.
Мейсон Уилер
4
@MasonWheeler: FORTRAN «слепо скопировал ошибку С»? Лично я считаю, что в хорошо спроектированных языках должны использоваться отдельные операторы для усеченного деления в сравнении с приблизительным делением целочисленных величин (а также, между прочим, для значения и ссылочного равенства), но проектное решение во времена Фортрана было, вероятно, разумным. Это не означает, что каждый язык должен делать то, что делал ФОРТРАН в 1950-х годах.
суперкат
3
Языки более низкого уровня должны делать эти различия. Языки более высокого уровня / динамические языки часто лучше без них. Это компромисс дизайна. Я ценю только один большой тупой конструктор / тип чисел в JS, но я думаю, что мне не хватало бы JS при попытке написать высокопроизводительный 3D-движок без более строгого управления типами. У JS может быть дублирующая утилита с другими языками, но я не думаю, что кто-либо когда-либо будет считать это ходом для написания высокопроизводительных вещей типа «ближе к хрому».
Эрик Реппен
2
Математика вне программирования рассматривает только целочисленные правила для деления.
Эрик Реппен
5
@MasonWheeler: программирование - это не чистая математика. Математически, 1/6 является рациональным числом и не может быть точно представлено двоичным числом с плавающей запятой. Единственное точное представление - это отношение со знаменателем, в шесть раз превышающим числитель.
Кевин Клайн
7

Есть много языков, где ((1/6)*6)результат равен 1, а не 0. Например, PL / SQL, многие диалекты BASIC, Lua.

Случайно, во всех этих языках 1/6 приводит к .166666667, или 0.16666666666667 или что-то подобное. Я выбрал вариант ((1/6) * 6) == 1, чтобы избавиться от этих небольших различий.

user281377
источник
7
Это не вопрос.
Рок Марти
20
Случайно, во всех этих языках 1/6 приводит к .166666667, или 0.16666666666667 или что-то подобное. Я выбрал ((1/6)*6)==1вариант, чтобы избавиться от этих небольших различий, но, похоже, я переоценил математические навыки некоторых людей.
user281377
1
@ RocMartí да, это действительно так ...
MattDavey
1
Я был бы удивлен, увидев (1.0 / 6.0) * 6 точно равным 1! Округление результата (1.0 / 6.0) приведет к небольшой разнице. (Хотя будет несколько языков, которые по умолчанию имеют бесконечную точность)
Sjoerd
1
@Sjoerd: Это не слишком удивительно, что это на самом деле точно. Рассмотрим в десятичном виде сценарий 1/11 * 11 со всеми значениями с точностью до пяти значащих цифр. Значение 1/11 составляет 9,0909 * 10 ^ -2. Умножьте на 11, и вы получите 99,9999 * 10 / -2 перед округлением. Округлите до пяти значащих цифр, и результат будет 1.0000 * 10 ^ 0. Обратите внимание, что ключ в том, что мантисса 1/6 - это "... 0101010101 ...". Если последний бит представления равен «1», умножение его на шесть и округление приведут к 1. Если последний бит был равен нулю, он не будет.
Supercat
3

Haskell рассматривает 1/6 и 1,0 / 6,0 как тождественно 0,16666666666666666. Он также отображает 1 / 6.0 и 1.0 / 6 как одно и то же значение.

Это связано с тем, что базовые числовые типы в Haskell не совпадают с другими языками. Истинное целочисленное деление несколько ... сложно.

Мировой инженер
источник
2

Да, Perl делает. Однострочник

perl -e '$x=1/6;print "$x\n";'

В результате получается:

0.166666666666667

Я считаю, что PHP работает так же.

Отредактировано, чтобы добавить: я также считаю, что необходимым (но не достаточным) условием 1/6 == 1.0/6.0является слабая типизация рассматриваемого языка.


источник
Почему, черт возьми, слабый набор текста (что бы это ни значило) был бы необходим? Просто определите / to (также) среднее деление с плавающей точкой в ​​случае, когда оба аргумента являются целыми числами.
1
@delnan - en.wikipedia.org/wiki/Weak_typing Я полагаю, что возможно иметь строго типизированный язык, который /автоматически перегружается в зависимости от типов аргументов, но это выглядит как нарушение принципа наименьшего удивления для я ...
2
Слабая / сильная типизация плохо определена (как подразумевает и вики), пожалуйста, избегайте ее и будьте конкретны. Я так понимаю, ваше определение запрещает неявное преобразование, но не специальный полиморфизм? Если это так, рассмотрим Haskell, у которого нет неявных преобразований, но довольно хорошо выполненный (например, работает в 99% случаев и может быть понят смертным) числовой полиморфизм. И почему это было бы удивительно? Было бы гораздо удивительнее (если не сказать раздражающим), если бы мне пришлось добавлять несколько точек для каждого отдельного экземпляра любого оператора, в зависимости от точности, которую я желаю.
2
@JackManey Я думаю, что для большинства новичков более удивительно, что 1/2 должно равняться 0, чем если деление двух целых чисел приводит к двойному. Ведь целые числа также не закрыты по делению в математике. Также, как указывает Делнан, Haskell является примером строго типизированного языка, в котором / с двумя целыми числами не производит целое число. И Python 3 - это другое.
sepp2k
1
Язык Haxe строго (хотя и выведен), но он не имеет целочисленного деления, только float. Итак, поехали.
Стефф
2

В Squeak Smalltalk /по целым числам создаются объекты Fraction. Таким образом, хотя это не то же самое, что и деление с плавающей точкой, все равно (1/6)*6возвращается 1.

Cephalopod
источник
Во всех производных Smalltalk-80 (то есть почти во всех Smalltalks). Янтарь - одно из современных исключений (что понятно, будучи скомпилированным в JavaScript).
Херби
2

Да, я только что проверил мой TI-99 / 4A , встроенный в TI BASIC . Поскольку все числовые выражения рассматриваются как плавающая точка, операция деления также является плавающей точкой.

 TI BASIC READY
>PRINT 1/6
  .1666666667

>
Джесси С. Слайсер
источник
2

MATLAB. Числовые литералы являются двойными по умолчанию.

>> 1/6
ans =
    0.1667
Дима
источник
2

Clojure использует дроби по умолчанию. Это не то же самое, что 1.0 / 6.0, но вы можете конвертировать в него с помощью floatили doubleкогда вам нужно.

user=> (/ 1 6)
1/6
user=> (* (/ 1 6) 2)
1/3
user=> (pos? (/ 1 6)) ; Is 1/6 > 0?
true
user=> (float (/ 1 6))
0.16666667
defhlt
источник
1

Удивительно, но в Windows PowerShell (версия 3) он работает правильно .

PS C:\> 1.0 / 6.0
0.166666666666667

PS C:\> 1/6
0.166666666666667

Также, похоже, работает в Python 3, как упоминалось sepp2k. Два других языка, которые у меня есть в наличии на REPL, Scala и Ruby, оба делят целочисленные и дают 0.

KChaloux
источник
0

Язык Rexx всегда дает арифметически правильный ответ. Например: 5/2 = 2,5. Rexx - отличный язык, который не был использован достаточно. Теоретически, когда компилятор не может определить, что вы хотите, лучше сделать правильную математику, однако это может быть неэффективно. Rexx также предоставляет оператор //.

Без шансов
источник