Дробная часть двойников без использования строковых манипуляций - PullRequest
3 голосов
/ 24 июня 2011

Есть ли способ получить дробные части Double без использования строковых манипуляций?В частности, в vb.net?

Существует множество примеров десятичной дроби на разных языках. Вот один для vb.net

В основном все они, похоже, используют либо мод, либо усеченный метод, либо используют приведение к целочисленному поведению.Но ни один из этих подходов не будет работать для типа double / float из-за присущей ему неточности типа double / float.Существует также проблема, заключающаяся в том, что двойные числа не приводятся к десятичным знакам.Ниже приведен тест для демонстрации того, что я имею в виду и ожидаю.

<TestClass> _
    Public Class BasicNumberTests

        Function DecimalFractionalPart(ByVal number As Decimal) As Decimal
            Dim wholePart As Decimal = Math.Truncate(number)
            Return number - wholePart
        End Function

        Function DoubleFractionalPart(ByVal number As Double) As Double
            Dim wholePart As Double = Math.Truncate(number)
            Return number - wholePart
        End Function

        <TestMethod()> Public Sub SplitDoubleAsDecimal1()
            Dim number As Double = 0.65 + 0.05
            Dim fractionalPart As Double = CDbl(DecimalFractionalPart(CDec(number)))
            Assert.AreEqual(0.70000000000000007, number)
            Assert.AreEqual(0.70000000000000007, fractionalPart) '<- Fails
        End Sub

        <TestMethod()> Public Sub SplitDoubleAsDecimal2()
            Dim number As Double = 0.70000000000000007
            Dim fractionalPart As Double = CDbl(DecimalFractionalPart(CDec(number)))

            Assert.AreEqual(0.70000000000000007, number)
            Assert.AreEqual(0.70000000000000007, fractionalPart) '<- Fails
        End Sub

        <TestMethod()> Public Sub SplitDoubleAsDouble1()
            Dim number As Double = 1.65

            Assert.AreEqual(1.65, number)
            Assert.AreEqual(0.65, DoubleFractionalPart(number)) '<- Fails
        End Sub

        <TestMethod()> Public Sub SplitDoubleAsDouble2()
            Dim number As Double = 1.0 + 0.65
            Assert.AreEqual(1.65, number)
            Assert.AreEqual(0.65, DoubleFractionalPart(number)) '<- Fails
        End Sub

End Class

Чтобы обойти это, у меня есть методы, которые используют манипуляции со строками для получения дробной части.Проблема в том, что он работает только с определенными типами форматирования.

Я задал этот вопрос (неверно) в другом месте и (до того, как мой вопрос был удален) кто-то сказал, что будет работать с двойными числами, которые я не понимал.

(Для ясности, я действительно понимаю, что двойник, который вы видите, не всегда точно соответствует действительному числу, как кажется. Я знаю, что .65 + .05 <>.7 Но я также знаю, что 0,70000000000000007 всегда = 0,70000000000000007 = .65 + .05 и т. Д. Но я хочу, чтобы дробная часть десятичной дроби КАК ЭТО БЫЛА.)

Я что-то упустил?Есть ли способ надежно получить дробную часть двойных чисел без использования манипуляции со строками?

Если я должен использовать манипулирование строками, как я могу это сделать, чтобы она всегда работала независимо от культурного форматирования?

РЕДАКТИРОВАТЬ-Тесты показывают, что я хочу.Если Double равен 0,70000000000000007, я хочу, чтобы метод возвращал 0,70000000000000007.Если это .65, я хочу .65 вернулся.Так как .65 + .05 = 0.70000000000000007, то GetFractionalParts (.65 + .05) должно вернуть 0.70000000000000007

1 Ответ

1 голос
/ 26 июня 2011

Как насчет:

REM Add: Imports System.Runtime.CompilerServices

<Extension()>
Public Function getFractionalPart(ByVal number As Single) As Single
    Return number - Math.Truncate(number)
End Function

<Extension()>
Public Function getFractionalPart(ByVal number As Double) As Double
    Return number - Math.Truncate(number)
End Function

<Extension()>
Public Function getFractionalPart(ByVal number As Decimal) As Decimal
    Return number - Math.Truncate(number)
End Function

Получено

Обратите внимание, что:

    Dim number As Double = 10 * 0.69
    Console.WriteLine(BitConverter.DoubleToInt64Bits(6.9D))
    Console.WriteLine(BitConverter.DoubleToInt64Bits(number))
    Console.WriteLine(BitConverter.DoubleToInt64Bits(6.9D.getFractionalPart()))
    Console.WriteLine(BitConverter.DoubleToInt64Bits(number.getFractionalPart()))

Распечатает:

    4619454727784602010
    4619454727784602009
    4606281698874543309
    4606281698874543304

Причинаэто означает, что 10 * 0.69 отличается от 6.9, потому что double является приблизительным значением.Также обратите внимание, что 10 * 0.69 и 6.9 отличаются только на 1 приращение мантиссы.Причиной, почему их дробная часть отличается более чем на 1 приращение мантиссы, является IEEE 754 - приращение не является постоянным.Значения, близкие к 0, более точны, и это «не ошибка, а особенность», поскольку они наиболее распространены.

Я не уверен, что вы хотите исправить.

Если выхотите распечатать correct значение, тогда вы можете использовать метод Джона Скита ToExactString: http://www.yoda.arachsys.com/csharp/DoubleConverter.cs

...